import os
import pandas as pd
import re
import json
import numpy as np
from openai import OpenAI
from file_encryptor import encryptor


def process_llm_history(config, paras): # under development, if we need to parse the history
    his_record = ''
    try:
        his_k = config['HISTORY_K']
        histories = paras['histories']
        
        if len(histories)>=2:
            his_infos = histories[-2 :]
            for i, his in enumerate(his_infos):
                his_record += f"""历史提问{i+1}：{his[0]}    历史回答{i+1}：{his[1]}\n"""
            his_record = his_record.strip()
    except:
        pass
    
    return his_record


def build_prompt(model='gpt-3.5-turbo-0125', **kwargs): # gpt-4-turbo-preview
    # try:
    #     client = OpenAI(
    #         # defaults to os.environ.get("OPENAI_API_KEY")
    #         api_key=kwargs['config']['GPT_APIKEY'],
    #         timeout=100
    #     )
    # except:
    #     print('Bad config inputs...')
    #     return None

    try:
        task = kwargs['paras']['task']
        query = kwargs['paras']['query']
        text = kwargs['paras']['texts']
    except:
        print('Bad paras inputs...')
        return None
        
    try:
        model = kwargs['paras']['model']
    except:
        pass

    print('\tcurrently using API-model: {}'.format(model))
    his_record = process_llm_history(kwargs['config'], kwargs['paras'])
    
    key_ = 'answer'
    temperature = 0.01
    top_p = 0.01
    max_token = 1500
    
    if task=='talk': # when generating response using KB
        add_req = kwargs['paras']['add_req']
        prompt = f"""
                你将接收到用户的问题：'''{query}'''
                你可能还会接收到历史对话中的相关问题和答案：'''{his_record}'''
                请严格参考历史记录（如有），回答用户问题。{add_req}
                你的返回只能是json形式，键为"answer"，值为你的回答，不要返回其他信息。
            """
            
    # elif task=='talk-kb': # when generating response using KB
    #     add_req = kwargs['paras']['add_req']
    #     example = '''
    #             {
    #                 "judge": "否",
    #                 "answer": "根据您的资料答案是..."
    #             }
    #         '''
    #     prompt = f"""
    #             你将接收到用户的问题：'''{query}'''
    #             你将接收到一些基础知识和资料：'''{text}'''
    #             你可能还会接收到历史对话中的相关问题和答案：'''{his_record}'''
    #             你要执行以下两步：
    #             1）判断输入的基础知识资料中是否包含了用户问题的答案。
    #             2）根据基础知识资料以及历史记录（如有）回答用户问题。{add_req} 
                
    #             你的返回必须是JSON字典格式，包含2个键，第1个键是"judge"，值为输入的基础知识资料中是否包含了用户问题的答案，只能“是”或“否"；第2个键是"answer"，值为你的回答，下面是一个返回示例
    #         """    
    #     prompt = prompt + example

    elif task=='talk-kb':
        add_req = kwargs['paras']['add_req']
        if_judge = kwargs['paras']['if_judge']
        max_token = kwargs['paras']['out_limit']
        temperature = 0
        top_p = 0.01
        
        if if_judge:
            example = '''
                    {
                        "judge": "否",
                        "answer": "基础资料暂无答案..."
                    }
                '''
            
            prompt = f"""
                    你将接收到用户的问题：'''{query}'''
                    你将接收到一些基础知识和资料：'''{text}'''
                    你可能还会接收到历史对话中的相关问题和答案：'''{his_record}'''
                    你要执行以下两步：
                    1）判断基础知识资料和历史回答中是否涉及用户问题的答案。
                    2）根据基础知识资料以及历史记录回答用户问题。{add_req}
                    
                    你的返回必须是JSON字典格式，包含2个键，第1个键是"judge"，值为基础知识资料或历史回答是否包含了用户问题的答案，只能“是”或“否"；第2个键是"answer"，值为你的回答，下面是一个返回示例
                """    
            prompt = prompt + example
        else:
            prompt = f"""
                    你将接收到用户的问题：'''{query}'''
                    你将接收到一些基础知识和资料：'''{text}'''
                    你可能还会接收到历史对话中的相关问题和答案：'''{his_record}'''
                    请严格参考基础知识资料以及历史记录（如有），回答用户问题。{add_req}
                    如果用户在找图片，且基础资料中存在该图片，直接返回图片的名字。
                """        
            
    elif task=='filling-tb': # when generating response using KB
        example = '''
                输入查询关键词：部门
                
                表格资料如下：
                    姓名               单位              部门       中心
                    吴承科    中国科学院深圳先进技术研究院    集成所    智能仿生中心
                
                输出答案
                {
                    "answer": "集成所"
                }
            '''
        prompt = f"""
                你的任务是填充表格信息
                
                你将接收到表格形式的基础资料：'''{text}'''
                请根据已有资料，找到对应查询关键词'''{query}'''的内容'''answer'''

                你的返回必须是JSON字典格式，键为"answer"，值为你找到的表格信息
            """    
        prompt = prompt + example

    elif task=='if-history':
        temperature = 0.01
        top_p = 0.01
        
        example_true = '{"trace_history":"是"}'
        example_false = '{"trace_history":"否"}'
        examples_full = '''
                        上一轮提问是：360安全大模型的技术方案包括哪些内容？
                        当前提问是：其中第二项技术方案的具体实现方式是是什么？
                        你应该判断当前提问是上一轮提问的追问，因此回答 {"trace_history":"是"}

                        上一轮提问是：我们单位的报销要求是什么？
                        当前提问是：我可以住多少的酒店？？
                        你应该判断当前提问是上一轮提问的追问，因此回答 {"trace_history":"是"}
                                                
                        上一轮提问是：基坑旁边是否可以存放施工材料？
                        当前提问是：室内管道的最低标高要求是多少？
                        你应该判断当前提问不是上一轮提问的追问，因此回答 {"trace_history":"否"}
        '''
        histories = kwargs['paras']['histories']
        if len(histories)>0:
            last_query = histories[-1][0]
        else:
            last_query = ''
            
        prompt = f"""
                你会收到当前用户的提问'''{query}'''
                你还会收到上一轮的用户问答记录'''{his_record}'''
                请判断，当前提问'''{query}'''是否为在上一轮问答的基础上的追加问题。
                你的回答为JSON字典格式，键为"trace_history"，值只能为"是"或者"否"，例如{example_true}或者{example_false}
                不要输出其他内容，回答不要超过5个字。下面给出了一些例子：
            """
        prompt = prompt + examples_full

    elif task=='summary': 
        add_req = kwargs['paras']['add_req']
        prompt = f"""
                you will be given a customer text delimited by triple quotes, which is written in Chinese.
                your task is to generate a summary of the text, using one sentence and no more than {other_texts} words.
                the response should be in JSON form, where the key is "answer", the value is the summarized text.
                the values in the JSON response should be in Chinese only.
                customer text: '''{text}''' {add_req}
            """
    
    elif task=='rewrite-paras':
        topic = kwargs['paras']['topic']
        example = '''
                {
                    "paras":
                    [
                        { "段落1标题": "段落1内容" },
                        { "段落2标题": "段落2内容" },
                        ...
                    ]
                }
            '''
        prompt =  f"""
                对下面这段输入的文本内容进行分段重写：'''{text}'''
                在重写过程中，你要做到以下几点：
                第一，检查文档中可用于分段的明显标识，比如“1、”和“2、”等数字，按这些标识将文本内容分段。
                第二，你的重写要符合当前的主题：{topic}，根据该主题可以对原文进行适当修改。
                第三，重写后的文本体量要与原文本相似，不能缩减内容。
                你的回答只能是json形式，其键为"paras"，值为一个列表，包含多个键值对；每个键值对代表一个段落，键为你提炼的段落标题，值为完整段落内容。
                除以上json返回外，不要返回其他多余信息。你的返回结果的示例如下（注意其中的标题和内容需要根据实际内容替换）：
            """
        prompt += example
        
    elif task=='parse-titles': # this one is NOT supported by API currently
        try:
            example = kwargs['paras']['example']
        except:
            example = ''
        temperature = 0
        
        if example.strip()=='':
            example = '''
            
                {
                  "ROOT": {
                    "一、项目背景": {
                      "（一）市场": {},
                      "（二）政策": {
                        "政策条款1": {},
                        "政策条款2": {}
                      }
                    },
                    "二、研究内容": {
                      "1. 研究内容": {
                        "具体技术1": {},
                        "具体技术2": {}
                      },
                      "2. 研究内容": {}
                    }
                  }
                }
                '''
        else:
            example = '对同一文件，你上一轮的解析结果如下：\n' + example + '\n参考上例中的标题层级结构规律，解析当前输入的标题'
        
        prompt = f"""
                你会收到一份文档的多个标题。
                你需要将输入的标题转化为JSON字典的树状结构，父节点是高层级标题，子叶节点是低层级的标题
                你生成的树状结构内，节点只能是输入的标题。
                你禁止删除任何输入标题中的起始数字和字母（比如1. 或 2. 等）
                你返回的JSON字典的根节点固定为"ROOT"，所有其他节点的键是标题名称，值是下层节点；如下层没有其他标题，则其值固定为空字典。
                一个例子如下：
            """
        prompt = prompt + '\n' + example + '\n' + text
        
    elif task=='reason': # 自动填充时选择TOPK中的一个
        example = '''
            {
                "match": 1
            }
        '''
        prompt = f"""
                你会收到一个列表，包含有多个主题，每个主题占一行，并使用【1】【2】【3】等标明了其位置，你还会收到一个查询意图。
                你需要从输入的主题中，推断哪个最符合输入的查询意图，并返回该主题的位置编号
                你的回答必须是JSON格式，键为"match"，值为最符合的主题的位置
                例如，输入主题列表为：'''
                【1】通用技术规划调度技术电动汽车调度模型
                【2】低碳新能源低碳交通新能源汽车政策
                【3】通用技术控制技术V2G车网协同控制,智慧电网政策
                【4】通用技术通用设备系统V2G系统架构'''
                
                查询意图为：'''电动汽车调度'''
                你应该发现位置为1的主题最符合要求，因此，你的返回如下例所示：
                 {example}
                
                现在，根据以上指示和示例，进行推断：
                输入主题列表: '''{text}'''
                查询意图：'''{query}'''
            """
        key_ = ['match']
  
    elif task=='judge-querypurpose': # 分析问题的目的
        purposes = kwargs['paras']['purposes']
        domain = kwargs['paras']['domain']
        # '\n'.join([f"{term}: {explanation}" for term, explanation in terms_dict.items()])
        
        prompt = f"""
                假设你是{domain}领域的专业人员，你将收到用户提问或需求：'''{query}'''
                你将收到一个字典，字典的键为用户提问的意图，值为对该意图的解释：'''{purposes}'''
                你的任务是判断哪个意图最符合用户当前的提问或需求。
                你的回答只能是json格式，主键为"purpose"，值为你识别到的用户意图。
            """
        key_ = 'purpose'
    
    elif task=='gen-ques':
        prompt = f"""
                你的角色是一名专为建设工程领域编制专业考试试卷的考官
                你会接收到一段知识内容：'''{text}'''
                你的任务是基于这段内容出一道问答题，注意：
                题干要能适合用于专业考试（如一级建造师、二级建造师等）；题干的表述要具体详细，且符合给定的知识内容，不要超过50个字
            """
                               
    elif task =='rerank':
        example = '''
                输入用户提问：'在进行管道埋设前的灌水试验和通水试验时，持续时间至少是多少'
                输入列表：'[【1】给排水工程验收，室外排水管网安装，排水管道安装，管道埋设前必须做灌水试验和通水试验， 排水应畅通，无堵塞，管接口无渗出, 【2】给排水工程验收规范，室外供热管网安装，系统水压试验及调试，供热管道作水压试验时，试验管道上的阀门应开启, 【3】城市桥梁工程施工与质量验收，灌注桩，灌注水下混凝土应符合下列规定：]'
                你的返回:
                {
                    【1】给排水工程验收，室外排水管网安装，排水管道安装，管道埋设前必须做灌水试验和通水试验， 排水应畅通，无堵塞，管接口无渗出 : 0,
                    【2】给排水工程验收规范，室外供热管网安装，系统水压试验及调试，供热管道作水压试验时，试验管道上的阀门应开启 : 1,
                    【3】城市桥梁工程施工与质量验收，灌注桩，灌注水下混凝土应符合下列规定： : 2
                }
            '''
        prompt = f"""
                你会接收到一个用户提问：'''{query}'''
                你还会接收到一个列表，其包含多个可能与这个提问相关的知识文件的描述：'''{text}'''
                
                你的任务是判断列表中每个知识文件与用户提问的相关性和匹配性，并按从高到低顺序，对列表中所有知识文件的路径重新排序。一个例子如下所示：
                {example}
                你返回的只能是一个JSON格式的字典，该字典的键为输入列表中的知识文件路径，值为其排序，排名越高，说明其与用户提问匹配性越高。
            """
    
    elif task=='inspect-typos':
        example = '''
                如果用户的输入存在错别字，例如“令天我们去工地按装一台设备”，则你的回答示例如下：
                {
                    "回答" : [
                        {"原词": "令天", "修正":"今天" },
                        {"原词": "按装", "修正":"安装" }
                    ]
                }
                
                如果用户输入不存在错别字，则修改列表为空，例如“工程项目的付款周期为3个月” 你的回答示例如下：
                {
                    "回答" : [ ]
                }
            '''
        
        prompt = f"""
                你会收到一段关于文字，你需要检查文中错别字。
                仅检查常用的动词、名词、形容词，仅检查明确的错别字，不要检查表达方式。
                你的回答必须是JSON形式，键为“回答”，值为一个列表，列表中每个元素均为字典，包含2个键，即"原词"和"修正"，对应的值为存在错别字的短语以及修改后的结果。
                禁止返回任何解释或注释，返回示例如下：
                {example}
                现在，按以上规则和示例，分析下文错别字： '''{text}'''
            """
    elif task=='correct-heading':
        example = '{"###第一章":"##第一章", "####第二章":"##第二章"}'
        prompt = f"""
                你会收到一段标题，你需要使用markdown语法修正标题的层次。
                你的回答必须是JSON形式，旧标题作为key，修正后标题作为value，禁止返回任何解释或注释
                返回示例如下：
                {example}
                现在，按以上规则和示例，修正这一段标题： '''{text}'''
            """
    # ****FOLLOWING ARE FOR QIANXUN PATENT SYSTEM****
    # elif task=='judge-speciality': # 量化打分的方法分析问题的性质
    #     domain = kwargs['paras']['domain']
    #     prompt = f"""
    #             假设你是{domain}领域的专业人员，你要评估用户提问或需求的“模糊”程度，并使用0-10整数从低到高打分。  
    #             其中，“模糊”问题，是指查询对象不明确，缺乏具体场景描述，内容简单的问题，比如：“多少柱子？”、“梁的偏差是多少？”、“用什么混凝土等？”、“有哪些规定？”；
    #             反之，“清晰”问题，是查询对象清晰明确，内容详细，且包含专业术语的问题，例如：“重庆实验小学项目一楼西南侧设计多少根构造柱？”、“桥梁主体钢梁安装的垂直偏差是多少？”、“小型火电厂地基处的混凝土强度最低是多少？”、“绿色产业中，如何激发各类市场主体创新活力？”、“在低压电网中，作为配电变压器的三相变压器的接线组别宜采用什么形式？”等。
    #
    #             现在，请基于以上规则和示例，分析用户提问的模糊程度：'''{query}'''
    #             你的回答是json格式，主键为"answer"，值为问题模糊程度得分。
    #         """
    #
    # elif task=='judge-complexity': # 量化打分的方法分析问题的性质
    #     domain = kwargs['paras']['domain']
    #     prompt = f"""
    #             假设你是{domain}领域的专业人员，你需要理解和评估你的用户提问，判断其是否为“复杂”问题。
    #             其中，“复杂”问题的定义，是指用户提问由多个问题的组合而成，而非单一问题。
    #             例如，“每台锅炉应至少设置多少台密封风机？其压头裕量至少是多少？”包含了2个问题，分别是“每台锅炉应至少设置多少台密封风机？”以及“每台锅炉压头裕量至少是多少？”，因此，这是一个复杂问题；而“根据工业信息化部的政策和统计，截至2020年底，我国节能环保产业产值约多少”是一个非复杂简单问题。
    #
    #             现在，请基于以上规则和示例，分析用户提问：'''{query}'''
    #             如果你认为该提问是一个复杂问题，你需要从中提取每个子问题，并返回一个列表；如果你认为不是复杂问题，直接将原始问题作为一个列表返回。       
    #             你的返回只能是json格式，主键为"querys"，值为问题列表。
    #         """    
    #     key_ = 'querys' 
    #
    # elif task=='judge-teammatch': # 判断团队技术实力
    #     prompt = f"""
    #             你会收到一段关于技术团队的描述：'''{text}'''
    #             你还会收到一个用户请求或提问：'''{other_texts}'''
    #
    #             你需要判断这个团队多大程度上可以满足用户的请求或提问，你需要用0-10分来表达你认为的满足程度。
    #             其中0分代表“完全不满足”，10分代表“完全满足”。
    #             你的打分主要考虑两方面，首先是对用户请求的匹配程度，其次是团队描述体现出的团队技术实力。
    #             你需要特别注意，一旦团队描述中没有直接涉及用户需求中的技术，那么你的打分应该显著降低，不应超过6分。
    #             根据以上原则，你需要给出你打分的理由。
    #
    #             你的回答是json格式，主键为"answer"，包含2个子键"score"和"reason"，第一个子键的值为你给出的满足度评分，只能是0-10之间的整数，第二个子健的值为你打分的理由。
    #         """
    #
    # elif task=='find-solutions': # query是问题描述而非功能描述，自动找技术解决方案
    #     try:
    #         num = kwargs['paras']['num']
    #     except:
    #         num = 2
    #     prompt = f"""
    #             你会收到一段关于用户遇到的某种故障或者困难的描述 '''{text}'''
    #             你需要从技术角度考虑，思考并返回{num}条技术方案，帮助用户解决其困难，你要重点考虑技术的可行性，技术方案中具体步骤及其执行逻辑。
    #             你的返回只能是JSON格式，键为"solutions"，值为你提出的技术方案列表。
    #             下面给出一个例子，如果用户给出的困难是：土方施工的进度难以估计，影响预算支付，你提出的方案可以如下
    #             {{
    #                 [
    #                     "使用无人机扫描现场，生成点云，基于点云生成3D模型，和土方预设模型（如BIM）对比计算进度差异"
    #                     "使用无人机巡检现场，拍摄大量照片然后使用摄影测量技术生成土方3D模型，和预设土方模型（如BIM）对比计算进度差异"
    #                 ]
    #             }}
    #         """
    #     key_ = 'solutions'
    #
    # elif task=='think-more-techniques': # query模糊时自动联想丰富
    #     try:
    #         num = kwargs['paras']['num']
    #     except:
    #         num = 3
    #     prompt = f"""
    #         You are the chief technology officer and a scientist.
    #         You will receive a short description: '''{text}'''. The description is about a technology or function and is written in Chinese.
    #         You should recommend no more than {num} technologies indispensable in terms of realizing the functions or technologies.
    #
    #         Your answer should include both your recommended technology and its simple introduction.
    #         The introduction should be specific and include details, instruments, equipment, models, algorithms, etc. For instance, if the function is to identify internal defects of concrete, you can recommend ground-penetration radar to collect data and Res-Net network to make predictions.
    #         Your response must follow JSON format, the key is 'thoughts'; the value is the list of recommended technologies separated by ','; in the list, each element includes a technology and its introduction, which are separated by '-'.
    #         Your response must be in Chinese.
    #
    #         For instance, if the input description is '''对风电机组所缺失的故障数据进行生成与故障预测'''
    #         the response could be like the following: 
    #         {{
    #             [
    #                 "物联感知技术-使用多类型传感设备监测机组和环境关键参数信息",
    #                 "对抗生成模型-一种机器学习模型，可以生成逼真的数据样本，可以用于生成风电机组的故障数据样本",
    #                 "BERT模型-一种基于编码解码结构的深度学习模型，可以对生成的故障数据进行训练，实现故障预测",
    #                 "数据预处理技术-对传感器数据进行预处理，识别关键特征等",
    #             ]
    #         }}
    #     """
    #     key_ = 'thoughts'
    #
    # elif task=='extract-points': # query复杂或者包含东西过多时提取技术点
    #     prompt = f"""
    #             你会收到一段关于用户输入的功能需求或技术描述：'''{text}'''
    #             你的任务是从中识别并提取最关键的技术点。
    #             你需要对识别到的技术点的表述进行专业化润色和丰富，使其尽可能贴近专业技术或科研人士的表述。
    #             你的回答是json格式，主键为"extracts"，值为一个列表，包含你识别到的技术点。
    #
    #             注意，你需要识别至少1个技术点，但不要超过3个，且要尽可能少。
    #             下面展示了json格式返回的一个例子，这里假设你识别到2个技术点，但实际上你可能识别到更多或更少。
    #             {{
    #                 [
    #                     技术点1, 技术点2, ...
    #                 ]
    #             }}
    #         """
    
    else:
        pass
    
    print('\tcurrent prompt length:', len(prompt))
    api_key = kwargs['config']['GPT_APIKEY']
    limit_len = 20000
    prompt = prompt[:limit_len] + '...' if len(prompt) > limit_len else prompt
    return prompt, model, temperature, top_p, max_token, api_key, key_   
            
def call_gpt(model='gpt-3.5-turbo-0125', **kwargs):
    def get_answer(prompt, MODEL, temperature, top_p, max_token, api_key, key_, **kwargs):
        try:
            client = OpenAI(
                # defaults to os.environ.get("OPENAI_API_KEY")
                api_key=api_key,
                timeout=100
            )
        except:
            print('Bad config inputs...')
            return None
        print('\tcurrent prompt length:', len(prompt))
        messages = [ { "role" :"user" , "content" : prompt } ]
        response = client.chat.completions.create(
            model=MODEL,
            messages=messages,
            temperature=temperature,
            top_p=top_p,
            # max_tokens=max_token
        )
        res_text = response.choices[0].message.content.strip()
        
        print(f'prompt:{prompt}, res_text:{res_text}')
        try:
            info_dic = json.loads(res_text)
            print('\tGPT answers initial format parsing success...')
        except:
            print('\tGPT answers initial format parsing fails, try extract json objects...')
            res_text = re.findall(r'```json\s+(.+?)\s+```', res_text, re.DOTALL)[0]
            info_dic = json.loads(res_text)

        try:
            cache_path = kwargs['paras']['cache_path']
            if encryptor.encrypt:
                encryptor.save_to_file(res_text, cache_path)
            else:
                with open(cache_path, encoding='utf-8', mode='w') as f:
                    json.dump(json.loads(res_text), f, ensure_ascii=False, indent=4)
        except Exception as e:
            print('\t', e)
        
        try:
            return info_dic[key_]
        except:
            return info_dic
        

    prompt, model, temperature, top_p, max_token, api_key, key_ = build_prompt(model, **kwargs)
    answer = get_answer(prompt, model, temperature, top_p, max_token, api_key, key_, **kwargs)
    return answer

from openai import OpenAI
from loguru import logger
SYSTEM_ROLE = '你是一位得力的助手，拥有渊博的行业知识。'
def call_gpt_stream(model='gpt-3.5-turbo-0125', **kwargs):
    def chat_gpt(prompt, model, temperature, top_p, max_tokens, qwen_apikey, stream=False, histories=[]):
        logger.debug('chat_gpt prompt:{}', prompt)
        messages=[{'role': 'system', 'content': SYSTEM_ROLE}, ]
        for history in histories:
            messages.append({"role": "user", "content": history[0]})
            messages.append({'role': 'assistant', 'content': history[1]})
        messages.append({ "role" :"user" , "content" : prompt})
        base_url = 'https://api.openai.com/v1'
        client = OpenAI(api_key=qwen_apikey, base_url=base_url)
        response_ = client.chat.completions.create(model=model, 
                                                    messages=messages, 
                                                    # max_tokens=max_tokens,
                                                    top_p=top_p,
                                                    # extra_body={'repetition_penalty': 1.2},
                                                    temperature=temperature,
                                                    stream=stream)
        if not stream:
            answer = response_.choices[0].message.content
            return answer
        
        res = ''
        old_ch = ''
        for chunk in response_:
            res_text = chunk.choices[0].delta.content
            # if res_text:
            #     res += res_text
            #     yield f'data: {res_text}\n\n'

            if res_text:
                # \n后面有空格会导致前端处理异常
                new_text = ''
                for ch in res_text:
                    if old_ch == '\n' and ch == ' ':
                        continue
                    new_text += ch
                    old_ch = ch
                res += res_text
                data = json.dumps({'text':new_text}, ensure_ascii=False)
                yield f'data: {data}\n\n'
        logger.debug('res:{}', res)

    prompt, model, temperature, top_p, max_token, api_key, key_ = build_prompt(model, **kwargs)
    yield from chat_gpt(prompt, model, temperature, top_p, max_token, api_key, stream=True)